import type { AxiosRequestConfig, AxiosResponse } from 'axios'

export interface RequestBatchConfig {
  maxBatchSize: number
  batchTimeout: number
  endpoint: string
}

export interface NetworkOptimizationConfig {
  enableRequestBatching: boolean
  enableResponseCompression: boolean
  enableRequestDeduplication: boolean
  enableConnectionPooling: boolean
  maxConcurrentRequests: number
  requestTimeout: number
  retryAttempts: number
  retryDelay: number
}

export interface BatchedRequest {
  id: string
  config: AxiosRequestConfig
  resolve: (value: any) => void
  reject: (error: any) => void
  timestamp: number
}

export interface RequestCache {
  [key: string]: {
    promise: Promise<any>
    timestamp: number
  }
}

class NetworkOptimizer {
  private config: NetworkOptimizationConfig
  private requestBatches = new Map<string, BatchedRequest[]>()
  private batchTimers = new Map<string, number>()
  private requestCache: RequestCache = {}
  private activeRequests = new Set<string>()
  private requestQueue: Array<() => Promise<any>> = []
  private processingQueue = false

  constructor(config: Partial<NetworkOptimizationConfig> = {}) {
    this.config = {
      enableRequestBatching: true,
      enableResponseCompression: true,
      enableRequestDeduplication: true,
      enableConnectionPooling: true,
      maxConcurrentRequests: 6,
      requestTimeout: 30000,
      retryAttempts: 3,
      retryDelay: 1000,
      ...config
    }
  }

  // Optimize axios request
  optimizeRequest<T = any>(
    config: AxiosRequestConfig,
    batchConfig?: RequestBatchConfig
  ): Promise<AxiosResponse<T>> {
    // Apply request deduplication
    if (this.config.enableRequestDeduplication) {
      const cacheKey = this.generateCacheKey(config)
      const cached = this.requestCache[cacheKey]
      
      if (cached && Date.now() - cached.timestamp < 5000) {
        return cached.promise
      }
    }

    // Apply request batching if configured
    if (this.config.enableRequestBatching && batchConfig) {
      return this.batchRequest(config, batchConfig)
    }

    // Apply connection pooling
    if (this.config.enableConnectionPooling) {
      return this.queueRequest(() => this.executeRequest(config))
    }

    return this.executeRequest(config)
  }

  // Batch similar requests
  private batchRequest<T = any>(
    config: AxiosRequestConfig,
    batchConfig: RequestBatchConfig
  ): Promise<AxiosResponse<T>> {
    return new Promise((resolve, reject) => {
      const requestId = this.generateRequestId()
      const batchedRequest: BatchedRequest = {
        id: requestId,
        config,
        resolve,
        reject,
        timestamp: Date.now()
      }

      // Get or create batch for this endpoint
      if (!this.requestBatches.has(batchConfig.endpoint)) {
        this.requestBatches.set(batchConfig.endpoint, [])
      }

      const batch = this.requestBatches.get(batchConfig.endpoint)!
      batch.push(batchedRequest)

      // Set up batch timer if not already set
      if (!this.batchTimers.has(batchConfig.endpoint)) {
        const timer = window.setTimeout(() => {
          this.processBatch(batchConfig.endpoint, batchConfig)
        }, batchConfig.batchTimeout)
        
        this.batchTimers.set(batchConfig.endpoint, timer)
      }

      // Process batch immediately if it reaches max size
      if (batch.length >= batchConfig.maxBatchSize) {
        this.processBatch(batchConfig.endpoint, batchConfig)
      }
    })
  }

  // Process batched requests
  private async processBatch(endpoint: string, batchConfig: RequestBatchConfig): Promise<void> {
    const batch = this.requestBatches.get(endpoint)
    if (!batch || batch.length === 0) return

    // Clear timer
    const timer = this.batchTimers.get(endpoint)
    if (timer) {
      clearTimeout(timer)
      this.batchTimers.delete(endpoint)
    }

    // Remove batch from map
    this.requestBatches.delete(endpoint)

    try {
      // Execute all requests in the batch
      const promises = batch.map(request => this.executeRequest(request.config))
      const responses = await Promise.allSettled(promises)

      // Resolve individual promises
      responses.forEach((result, index) => {
        const request = batch[index]
        if (result.status === 'fulfilled') {
          request.resolve(result.value)
        } else {
          request.reject(result.reason)
        }
      })
    } catch (error) {
      // Reject all requests in case of batch failure
      batch.forEach(request => {
        request.reject(error)
      })
    }
  }

  // Queue request for connection pooling
  private queueRequest<T>(requestFn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.requestQueue.push(async () => {
        try {
          const result = await requestFn()
          resolve(result)
        } catch (error) {
          reject(error)
        }
      })

      this.processQueue()
    })
  }

  // Process request queue
  private async processQueue(): Promise<void> {
    if (this.processingQueue || this.requestQueue.length === 0) return
    if (this.activeRequests.size >= this.config.maxConcurrentRequests) return

    this.processingQueue = true

    while (
      this.requestQueue.length > 0 && 
      this.activeRequests.size < this.config.maxConcurrentRequests
    ) {
      const requestFn = this.requestQueue.shift()!
      const requestId = this.generateRequestId()
      
      this.activeRequests.add(requestId)
      
      // Execute request without awaiting
      requestFn().finally(() => {
        this.activeRequests.delete(requestId)
        // Continue processing queue
        setTimeout(() => this.processQueue(), 0)
      })
    }

    this.processingQueue = false
  }

  // Execute single request with optimizations
  private async executeRequest<T = any>(
    config: AxiosRequestConfig
  ): Promise<AxiosResponse<T>> {
    // Apply compression headers
    if (this.config.enableResponseCompression) {
      config.headers = {
        ...config.headers,
        'Accept-Encoding': 'gzip, deflate, br'
      }
    }

    // Apply timeout
    config.timeout = config.timeout || this.config.requestTimeout

    // Cache the request if deduplication is enabled
    let cacheKey: string | null = null
    if (this.config.enableRequestDeduplication) {
      cacheKey = this.generateCacheKey(config)
    }

    try {
      // Import axios dynamically to avoid circular dependencies
      const { default: axios } = await import('axios')
      
      const promise = this.executeWithRetry(() => axios(config))
      
      // Cache the promise
      if (cacheKey) {
        this.requestCache[cacheKey] = {
          promise,
          timestamp: Date.now()
        }
      }

      const response = await promise

      return response
    } catch (error) {
      // Remove from cache on error
      if (cacheKey && this.requestCache[cacheKey]) {
        delete this.requestCache[cacheKey]
      }
      throw error
    }
  }

  // Execute request with retry logic
  private async executeWithRetry<T>(
    requestFn: () => Promise<T>,
    attempt = 1
  ): Promise<T> {
    try {
      return await requestFn()
    } catch (error: any) {
      if (attempt >= this.config.retryAttempts) {
        throw error
      }

      // Check if error is retryable
      if (!this.isRetryableError(error)) {
        throw error
      }

      // Wait before retry
      await this.delay(this.config.retryDelay * attempt)
      
      return this.executeWithRetry(requestFn, attempt + 1)
    }
  }

  // Check if error is retryable
  private isRetryableError(error: any): boolean {
    if (!error.response) {
      // Network errors are retryable
      return true
    }

    const status = error.response.status
    // Retry on server errors and rate limiting
    return status >= 500 || status === 429
  }

  // Generate cache key for request
  private generateCacheKey(config: AxiosRequestConfig): string {
    const key = {
      method: config.method || 'GET',
      url: config.url,
      params: config.params,
      data: config.data
    }
    return btoa(JSON.stringify(key))
  }

  // Generate unique request ID
  private generateRequestId(): string {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  }

  // Utility delay function
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  // Clean up expired cache entries
  cleanupCache(): void {
    const now = Date.now()
    const maxAge = 60000 // 1 minute

    Object.keys(this.requestCache).forEach(key => {
      const entry = this.requestCache[key]
      if (now - entry.timestamp > maxAge) {
        delete this.requestCache[key]
      }
    })
  }

  // Get optimization statistics
  getStats() {
    return {
      activeRequests: this.activeRequests.size,
      queuedRequests: this.requestQueue.length,
      cachedRequests: Object.keys(this.requestCache).length,
      activeBatches: this.requestBatches.size
    }
  }

  // Clear all caches and queues
  reset(): void {
    this.requestCache = {}
    this.requestQueue = []
    this.activeRequests.clear()
    this.requestBatches.clear()
    
    // Clear all timers
    this.batchTimers.forEach(timer => clearTimeout(timer))
    this.batchTimers.clear()
  }
}

// Create singleton instance
export const networkOptimizer = new NetworkOptimizer()

// Export for testing or custom instances
export { NetworkOptimizer }

// Utility functions for data compression
export class DataCompressor {
  // Compress JSON data
  static compressJSON(data: any): string {
    const jsonString = JSON.stringify(data)
    
    // Simple compression using repeated pattern replacement
    let compressed = jsonString
    
    // Replace common patterns
    const patterns = [
      ['"messageId":', '"mId":'],
      ['"conversationId":', '"cId":'],
      ['"fromUserId":', '"fId":'],
      ['"toUserId":', '"tId":'],
      ['"messageType":', '"mType":'],
      ['"createdAt":', '"cAt":'],
      ['"content":', '"cnt":'],
      ['"status":', '"st":'],
      ['"isRead":', '"rd":']
    ]
    
    patterns.forEach(([pattern, replacement]) => {
      compressed = compressed.replace(new RegExp(pattern, 'g'), replacement)
    })
    
    return compressed
  }

  // Decompress JSON data
  static decompressJSON(compressed: string): any {
    // Reverse the compression patterns
    const patterns = [
      ['"mId":', '"messageId":'],
      ['"cId":', '"conversationId":'],
      ['"fId":', '"fromUserId":'],
      ['"tId":', '"toUserId":'],
      ['"mType":', '"messageType":'],
      ['"cAt":', '"createdAt":'],
      ['"cnt":', '"content":'],
      ['"st":', '"status":'],
      ['"rd":', '"isRead":']
    ]
    
    let decompressed = compressed
    patterns.forEach(([replacement, pattern]) => {
      decompressed = decompressed.replace(new RegExp(replacement, 'g'), pattern)
    })
    
    return JSON.parse(decompressed)
  }

  // Calculate compression ratio
  static getCompressionRatio(original: string, compressed: string): number {
    return (1 - compressed.length / original.length) * 100
  }
}